// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library test.class_equality_test;

import 'dart:mirrors';

import 'package:expect/expect.dart';

class A<T> {}

class B extends A<int> {}

class BadEqualityHash {
  int count = 0;
  bool operator ==(other) => true;
  int get hashCode => count++;
}

typedef bool Predicate(Object o);
Predicate somePredicate = (Object o) => false;

checkEquality(List<Map<String, Object?>> equivalenceClasses) {
  for (var equivalenceClass in equivalenceClasses) {
    equivalenceClass.forEach((name, member) {
      equivalenceClass.forEach((otherName, otherMember) {
        // Reflexivity, symmetry and transitivity.
        Expect.equals(member, otherMember, "$name == $otherName");
        Expect.equals(
          member.hashCode,
          otherMember.hashCode,
          "$name.hashCode == $otherName.hashCode",
        );
      });
      for (var otherEquivalenceClass in equivalenceClasses) {
        if (otherEquivalenceClass == equivalenceClass) continue;
        otherEquivalenceClass.forEach((otherName, otherMember) {
          Expect.notEquals(
            member,
            otherMember,
            "$name != $otherName",
          ); // Exclusion.
          // Hash codes may or may not be equal.
        });
      }
    });
  }
}

void subroutine() {}

main() {
  LibraryMirror thisLibrary = currentMirrorSystem().findLibrary(
    const Symbol('test.class_equality_test'),
  );

  var o1 = new Object();
  var o2 = new Object();

  var badEqualityHash1 = new BadEqualityHash();
  var badEqualityHash2 = new BadEqualityHash();

  checkEquality(<Map<String, Object?>>[
    {'reflect(o1)': reflect(o1), 'reflect(o1), again': reflect(o1)},
    {'reflect(o2)': reflect(o2), 'reflect(o2), again': reflect(o2)},
    {
      'reflect(badEqualityHash1)': reflect(badEqualityHash1),
      'reflect(badEqualityHash1), again': reflect(badEqualityHash1),
    },
    {
      'reflect(badEqualityHash2)': reflect(badEqualityHash2),
      'reflect(badEqualityHash2), again': reflect(badEqualityHash2),
    },
    {'reflect(true)': reflect(true), 'reflect(true), again': reflect(true)},
    {'reflect(false)': reflect(false), 'reflect(false), again': reflect(false)},
    {'reflect(null)': reflect(null), 'reflect(null), again': reflect(null)},
    {
      'reflect(3.5+4.5)': reflect(3.5 + 4.5),
      'reflect(6.5+1.5)': reflect(6.5 + 1.5),
    },
    {'reflect(3+4)': reflect(3 + 4), 'reflect(6+1)': reflect(6 + 1)},
    {'reflect("foo")': reflect("foo"), 'reflect("foo"), again': reflect("foo")},
    {
      'currentMirrorSystem().voidType': currentMirrorSystem().voidType,
      'thisLibrary.declarations[#subroutine].returnType':
          (thisLibrary.declarations[#subroutine] as MethodMirror).returnType,
    },
    {
      'currentMirrorSystem().dynamicType': currentMirrorSystem().dynamicType,
      'thisLibrary.declarations[#main].returnType':
          (thisLibrary.declarations[#main] as MethodMirror).returnType,
    },
    {
      'reflectClass(A)': reflectClass(A),
      'thisLibrary.declarations[#A]': thisLibrary.declarations[#A],
      'reflect(new A<int>()).type.originalDeclaration': reflect(
        new A<int>(),
      ).type.originalDeclaration,
    },
    {
      'reflectClass(B).superclass': reflectClass(B).superclass,
      'reflect(new A<int>()).type': reflect(new A<int>()).type,
    },
    {
      'reflectClass(B)': reflectClass(B),
      'thisLibrary.declarations[#B]': thisLibrary.declarations[#B],
      'reflect(new B()).type': reflect(new B()).type,
    },
    {
      'reflectClass(BadEqualityHash).declarations[#==]': reflectClass(
        BadEqualityHash,
      ).declarations[#==],
      'reflect(new BadEqualityHash()).type.declarations[#==]': reflect(
        new BadEqualityHash(),
      ).type.declarations[#==],
    },
    {
      'reflectClass(BadEqualityHash).declarations[#==].parameters[0]':
          (reflectClass(BadEqualityHash).declarations[#==] as MethodMirror)
              .parameters[0],
      'reflect(new BadEqualityHash()).type.declarations[#==].parameters[0]':
          (reflect(new BadEqualityHash()).type.declarations[#==]
                  as MethodMirror)
              .parameters[0],
    },
    {
      'reflectClass(BadEqualityHash).declarations[#count]': reflectClass(
        BadEqualityHash,
      ).declarations[#count],
      'reflect(new BadEqualityHash()).type.declarations[#count]': reflect(
        new BadEqualityHash(),
      ).type.declarations[#count],
    },
    {
      'reflectType(Predicate)': reflectType(Predicate),
      'thisLibrary.declarations[#somePredicate].type':
          (thisLibrary.declarations[#somePredicate] as VariableMirror).type,
    },
    // MirrorReference.== does not accept type variables.
    // {
    //   'reflectClass(A).typeVariables.single':
    //       reflectClass(A).typeVariables.single,
    //   'reflect(new A<int>()).type.originalDeclaration.typeVariables.single':
    //       reflect(new A<int>()).type.originalDeclaration.typeVariables.single
    // },
    {'currentMirrorSystem()': currentMirrorSystem()},
    {'currentMirrorSystem().isolate': currentMirrorSystem().isolate},
    {
      'thisLibrary': thisLibrary,
      'reflectClass(A).owner': reflectClass(A).owner,
      'reflectClass(B).owner': reflectClass(B).owner,
      'reflect(new A()).type.owner': reflect(new A()).type.owner,
      'reflect(new B()).type.owner': reflect(new B()).type.owner,
    },
  ]);
}
